//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<debug.cpp>
///	@path	~/src/lib/utilities/
///	@date	2007/06/22
///	@desc	some debug helper functions.

#include "config/config.h"

#include "lib/utilities/debug.h"
#include "lib/utilities/string.h"
#include "lib/utilities/time.h"
#include "lib/utilities/observer.h"
#include "lib/system/lock.h"

#include <DbgHelp.h>

#include <deque>
#include <map>
#include <vector>

TRACE_CATEGORY( _S("Debug/trace") , TRACE_LV_INFO );

#define	DEBUG_BUFFER_MAX		512

namespace xeres {

	typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
		CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
		CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
		CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

	struct CrashContext
	{
		MINIDUMPWRITEDUMP		writeDump;
		EXCEPTION_POINTERS *	exceptionInfo;
	};

	// trace output
	namespace
	{
		// global trace
		void debug_out( const wchar_t * msg )
		{
			::OutputDebugString( msg );
		}

		void defaultFatalHandler( const wchar_t * info , const wchar_t * file , const wchar_t * function )
		{
#ifdef _DEBUG
			::abort();
#else
			::ExitProcess( -1 );
#endif // _DEBUG
		}

		FatalHandlerFn	s_fatalHandler = defaultFatalHandler;
		CrashHandlerFn	s_crashHandler = NULL;

		void __CrashCallback( const wchar_t * filename , CrashContext * context )
		{
			HANDLE hFile = ::CreateFile( filename, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, CREATE_ALWAYS,
				FILE_ATTRIBUTE_NORMAL, NULL );

			_MINIDUMP_EXCEPTION_INFORMATION ExInfo;

			ExInfo.ThreadId = ::GetCurrentThreadId();
			ExInfo.ExceptionPointers = context->exceptionInfo;
			ExInfo.ClientPointers = NULL;

			// write the dump
			context->writeDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL );

			::CloseHandle(hFile);
		}

		LONG WINAPI __UnhandledExceptionFilter( EXCEPTION_POINTERS * ExceptionInfo )
		{
			if( s_crashHandler == NULL )
				return EXCEPTION_CONTINUE_SEARCH;

			static bool has_processed = false;
			if( has_processed )
				return EXCEPTION_EXECUTE_HANDLER;
			has_processed = true;

			HMODULE hDll = ::LoadLibrary( _S("dbghelp.dll") );
			if( hDll == NULL )
				return EXCEPTION_CONTINUE_SEARCH;

			CrashContext context;
			context.exceptionInfo = ExceptionInfo;
			context.writeDump = (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" );
			if( context.writeDump == NULL )
				return EXCEPTION_CONTINUE_SEARCH;

			s_crashHandler( __CrashCallback , &context );
			return EXCEPTION_CONTINUE_SEARCH;
		}
	}

	// fatal abort
	void fatal_abort( const wchar_t * info , const wchar_t * file , const wchar_t * function )
	{
		if( s_fatalHandler )
		{
			s_fatalHandler( info , file , function );
		}
		::ExitProcess( -1 );
	}

	// set_fatal_handler
	void set_fatal_handler( FatalHandlerFn handler )
	{
		s_fatalHandler = handler;
	}

	// set_crash_dump_handler
	void set_crash_dump_handler( CrashHandlerFn handler )
	{
		s_crashHandler = handler;

		// register crash dump
		::SetUnhandledExceptionFilter( __UnhandledExceptionFilter );
	}

	// trace category

	namespace
	{
		// TODO: should we use DirTree instead ?
		struct TraceSection
		{
			TraceLevel m_level;
			WString m_name;
			std::vector< TraceSection * > m_subSections;

			~TraceSection( void )
			{
				for( size_t i = 0 ; i < m_subSections.size() ; ++i )
				{
					delete m_subSections[i];
				}
			}

			// define section if not exists, or return existed.
			TraceLevel * DefineSection( const StringSeq& seq , int current )
			{
				// totally matched
				if( current == seq.size() )
				{
					return &m_level;
				}
				// get next dir
				const WString& sub = seq[ current ];
				// match from sub
				for( size_t i = 0 ; i < m_subSections.size() ; ++i )
				{
					if( m_subSections[i]->m_name == sub )
					{
						return m_subSections[i]->DefineSection( seq , current + 1 );
					}
				}
				// not found, define new section
				TraceSection * newSec = new TraceSection;
				newSec->m_name = sub;
				newSec->m_level = TRACE_LV_TRACE;
				m_subSections.push_back( newSec );
				return newSec->DefineSection( seq , current + 1 );
			}

			// instance
			static TraceSection& instance( void )
			{
				static TraceSection s_rootDebugSection;
				return s_rootDebugSection;
			}
		};


		// define section
		TraceLevel * define_trace_section( const WString& category , TraceLevel level )
		{
			// break into seq
			StringSeq seq;
			break_string( category , '/' , seq , false );

			// init root section if not inited
			if( TraceSection::instance().m_name.empty() )
			{
				TraceSection::instance().m_name = _S("root");
				TraceSection::instance().m_level = TRACE_LV_TRACE;
			}

			TraceLevel * id = TraceSection::instance().DefineSection( seq , 0 );
			*id = level;
			return id;
		}
	}

	// ctor
	TraceCategory::TraceCategory( const wchar_t * category , TraceLevel default_level )
		: m_category( category )
	{
		m_traceCategoryLevel = define_trace_section( m_category , default_level );
	}

	// debug output

	// lowest level trace info output (direct output)
	void trace_puts( const wchar_t * info )
	{
		// FIXME! should be a verification here, not assert!
		assert( info );
		debug_out( info );
	}

	// debug info with va list
	void trace_vprintf( const wchar_t * fmt , va_list ap )
	{
		wchar_t buf[DEBUG_BUFFER_MAX];
		vswprintf_s( buf , DEBUG_BUFFER_MAX , fmt , ap );
		trace_puts( buf );
	}

	// debug info with arg
	void trace_printf( const wchar_t * fmt , ... )
	{
		va_list ap;
		va_start( ap , fmt );
		trace_vprintf( fmt , ap );
		va_end(ap);
	}

	namespace
	{
		static TraceCategory __defaultCategory( _S("Unclassified") , TRACE_LV_TRACE );
	}

	TraceOutput::TraceOutput( TraceLevel level , const TraceCategory * category )
		: m_level( level )
		, m_category( category == NULL ? __defaultCategory : *category )
	{
	}

	// dispatching debug output to several priority

	// trace handler
	namespace
	{
		ThreadLock s_traceHandlerLock;
		std::vector< function<void(TraceLevel,const wchar_t*)> > s_traceHandlers;
	}

	// add trace handler
	int add_trace_handler( function<void(TraceLevel,const wchar_t*)> callback )
	{
		LockGuard<ThreadLock> s_locker( s_traceHandlerLock );
		s_traceHandlers.push_back( callback );
		return (int)(s_traceHandlers.size() - 1);
	}

	// del trace handler
	void del_trace_handler( int id )
	{
		LockGuard<ThreadLock> s_locker( s_traceHandlerLock );
		assert( (int)s_traceHandlers.size() > id );
		s_traceHandlers[id] = NULL;
	}

	// routines

	// trace_stdout
	void trace_stdout( const char * msg )
	{
		WString buffer;
		mbs_to_wcs( msg , buffer );
		debug_out( buffer.c_str() );

		if( s_traceHandlers.size() > 0 )
		{
			LockGuard<ThreadLock> s_locker( s_traceHandlerLock );
			for( size_t i = 0 ; i < s_traceHandlers.size() ; ++i )
			{
				if( s_traceHandlers[i] != NULL )
					s_traceHandlers[i]( TRACE_LV_STDOUT , buffer.c_str() );
			}
		}
	}

	// trace_stderr
	void trace_stderr( const char * msg )
	{
		WString buffer;
		mbs_to_wcs( msg , buffer );
		debug_out( buffer.c_str() );

		if( s_traceHandlers.size() > 0 )
		{
			LockGuard<ThreadLock> s_locker( s_traceHandlerLock );
			for( size_t i = 0 ; i < s_traceHandlers.size() ; ++i )
			{
				if( s_traceHandlers[i] != NULL )
					s_traceHandlers[i]( TRACE_LV_STDERR , buffer.c_str() );
			}
		}
	}

	// get trace level prefix
	static inline const wchar_t * trace_level_prefix( TraceLevel level ) {
		static const wchar_t * s_tracePrefix[] = {
			_S("TRACE:  ") ,
			_S("DEBUG:  ") ,
			_S("INFO:   ") ,
			_S("WARNING:") ,
			_S("ERROR:  ") ,
			_S("FATAL:  ") ,
			_S("SCRIPT: ") ,
			_S("ASSERT: ") };

		if( level >= 0 && level < TRACE_LV_last )
			return s_tracePrefix[ level ];
		else
			return _S("");
	};

	void trace_print_pri( TraceLevel level , const wchar_t * fmt , va_list ap )
	{
		const wchar_t * prefix = trace_level_prefix( level );
		size_t fmt_len = wcslen( fmt );
		size_t len = wcslen( prefix ) + fmt_len + 2;
		wchar_t * p_fmt = static_cast<wchar_t*>(_alloca( len * sizeof(wchar_t) ) );
		wcscpy( p_fmt , prefix );
		wcscat( p_fmt , fmt );
		if( fmt[fmt_len] != '\n' )
			wcscat( p_fmt , _S("\n") );
		
		wchar_t buf[DEBUG_BUFFER_MAX];
		vswprintf_s( buf , DEBUG_BUFFER_MAX , p_fmt , ap );
		trace_puts( buf );

		// dispatch to handlers

		if( s_traceHandlers.size() > 0 )
		{
			LockGuard<ThreadLock> s_locker( s_traceHandlerLock );
			for( size_t i = 0 ; i < s_traceHandlers.size() ; ++i )
			{
				if( s_traceHandlers[i] != NULL )
					s_traceHandlers[i]( level , buf );
			}
		}
	}

	void TraceOutput::Trace( const wchar_t * fmt , ... )
	{
		// check if message needs to be filtered out
		if( m_level <= m_category.GetLevel() )
		{
			return;
		}

		va_list ap;
		va_start( ap , fmt );
		trace_print_pri( m_level , fmt , ap );
		va_end(ap);
	}

	// raise exception to cause end
	void raise_exception( void )
	{
		// FIXME! attach exception information
		// to give a change for program to send error message
		::RaiseException( EXCEPTION_ACCESS_VIOLATION , EXCEPTION_NONCONTINUABLE , 0 , 0 );		
	}

#if XERES_DEBUG_ENABLE_TRACKING

#ifdef _DLL
#error Function checking is not working on dll!
#endif

	// ctor
	TrackFunctionScope::TrackFunctionScope( const wchar_t * function , const wchar_t * file , const int line )
	{
		track_scope_begin( function , file , line );
	}

	// with msg
	void TrackFunctionScope::WithMessage( const wchar_t * fmt , ... )
	{
		va_list ap;
		va_start( ap , fmt );
		track_scope_msgv( fmt , ap );
		va_end( ap );
	}

	// dtor
	TrackFunctionScope::~TrackFunctionScope( void )
	{
		track_scope_end();
	}

	// call tracker
	struct call_tracker
	{
		WString		m_function;
		WString		m_file;
		WString		m_msg;
		DateTime	m_time;
		int			m_line_no;
	};

	// calls
	namespace
	{
		typedef std::deque< call_tracker > FuncTracker;

		struct ThreadLocalHolder
		{
		public:

			/// \instance
			static ThreadLocalHolder& instance( void )
			{
				static ThreadLocalHolder s_threadLocalInstance;
				return s_threadLocalInstance;
			}

			/// \brief	Get current holder.
			FuncTracker * Get( DWORD threadid )
			{
				std::map<DWORD,FuncTracker *>::iterator it = m_storage.find(threadid);
				if( it == m_storage.end() )
				{
					FuncTracker * t = new FuncTracker;
					m_storage[threadid] = t;
					return t;
				}
				return it->second;
			}

			/// \brief	Clear all.
			void clear( void )
			{
				for( std::map<DWORD,FuncTracker *>::iterator it = m_storage.begin() , end = m_storage.end() ;
					it != end ; ++it )
				{
					delete it->second;
				}
				m_storage.clear();
			}

		private:

			std::map<DWORD,FuncTracker *> m_storage;
		};
	}

	// for scope track entrance
	void track_scope_begin( const wchar_t * function , const wchar_t * file , const int line )
	{
		call_tracker ct;
		ct.m_function = function;
		ct.m_file = file;
		ct.m_time = DateTime::Now();
		ct.m_line_no = line;

		DWORD id = ::GetCurrentThreadId();
		ThreadLocalHolder::instance().Get( id )->push_back( ct );
	}
	
	// for scope track exit
	void track_scope_end( void )
	{
		DWORD id = ::GetCurrentThreadId();
		ThreadLocalHolder::instance().Get( id )->pop_back();
	}

	// attach msg to current function tracker
	void track_scope_msgv( const wchar_t * fmt , va_list ap )
	{
		wchar_t buffer[256];
		wvsprintf( buffer , fmt , ap );
		DWORD id = ::GetCurrentThreadId();
		FuncTracker * tracker = ThreadLocalHolder::instance().Get( id );
		assert( tracker->size() > 0 );
		call_tracker& ct = *(tracker->rbegin());
		ct.m_msg = buffer;
	}
	void track_scope_msg( const wchar_t * fmt , ... )
	{
		va_list ap;
		va_start( ap , fmt );
		track_scope_msgv( fmt , ap );
		va_end( ap );
	}

	// get current track depth
	int track_scope_depth( void )
	{
		DWORD id = ::GetCurrentThreadId();
		return (int)ThreadLocalHolder::instance().Get( id )->size();
	}

	// dump call stack
	void track_scope_dump( FILE * file )
	{
		// recovered from stack overflow
		DWORD id = ::GetCurrentThreadId();
		FuncTracker * tracker = ThreadLocalHolder::instance().Get( id );

		fputws( _S("xeres.engine call stack output.\n\n") , file );
		fputws( _S("Call stack as below: \n") , file );
		for( size_t i = 0 ; i < tracker->size() ; ++i )
		{
			DateTimePartition dtp;
			(*tracker)[i].m_time.PartitionDateTime( dtp );
			fwprintf( file , _S(" called: \t%s (%s)\t\t at %i:%i:%i.") ,
				(*tracker)[i].m_function.c_str() ,
				(*tracker)[i].m_msg.c_str() ,
				dtp.nHour , dtp.nMinute , dtp.nSecond );
		}
		fputws( _S("call stack list end.\n\n") , file );
	}
	// track_scope_clear_all
	void track_scope_clear_all( void )
	{
		ThreadLocalHolder::instance().clear();
	}
#endif // XERES_DEBUG_ENABLE_TRACKING

} // namespace xeres
